/**
 *  Ajax Autocomplete for jQuery, version %version%
 *  (c) 2017 Tomas Kirda
 *
 *  Ajax Autocomplete for jQuery is freely distributable under the terms of an MIT-style license.
 *  For details, see the web site: https://github.com/devbridge/jQuery-Autocomplete
 */

// Expose plugin as an AMD module if AMD loader is present:
(function (factory) {
    "use strict";
    if (typeof define === "function" && define.amd) {
        // AMD. Register as an anonymous module.
        define(["jquery"], factory);
    } else if (typeof exports === "object" && typeof require === "function") {
        // Browserify
        factory(require("jquery"));
    } else {
        // Browser globals
        factory(jQuery);
    }
})(
    /**
     * @param {jQuery} $
     */
    function ($) {
        "use strict";

        var utils = {
                escapeRegExChars: function (value) {
                    return value.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&");
                },
                createNode: function (containerClass) {
                    var div = document.createElement("div");
                    div.className = containerClass;
                    div.style.position = "absolute";
                    div.style.display = "none";
                    return div;
                },
            },
            keys = { ESC: 27, TAB: 9, RETURN: 13, LEFT: 37, UP: 38, RIGHT: 39, DOWN: 40 },
            noop = $.noop;

        function Autocomplete(el, options) {
            var that = this;

            // Shared variables:
            that.element = el;
            that.el = $(el);
            that.suggestions = [];
            that.badQueries = [];
            that.selectedIndex = -1;
            that.currentValue = that.element.value;
            that.timeoutId = null;
            that.cachedResponse = {};
            that.onChangeTimeout = null;
            that.onChange = null;
            that.isLocal = false;
            that.suggestionsContainer = null;
            that.noSuggestionsContainer = null;
            that.options = $.extend(true, {}, Autocomplete.defaults, options);
            that.classes = {
                selected: "autocomplete-selected",
                suggestion: "autocomplete-suggestion",
            };
            that.hint = null;
            that.hintValue = "";
            that.selection = null;

            // Initialize and set options:
            that.initialize();
            that.setOptions(options);
        }

        Autocomplete.utils = utils;

        $.Autocomplete = Autocomplete;

        Autocomplete.defaults = {
            ajaxSettings: {},
            autoSelectFirst: false,
            appendTo: "body",
            serviceUrl: null,
            lookup: null,
            onSelect: null,
            onHint: null,
            width: "auto",
            minChars: 1,
            maxHeight: 300,
            deferRequestBy: 0,
            params: {},
            formatResult: _formatResult,
            formatGroup: _formatGroup,
            delimiter: null,
            zIndex: 9999,
            type: "GET",
            noCache: false,
            onSearchStart: noop,
            onSearchComplete: noop,
            onSearchError: noop,
            preserveInput: false,
            containerClass: "autocomplete-suggestions",
            tabDisabled: false,
            dataType: "text",
            currentRequest: null,
            triggerSelectOnValidInput: true,
            preventBadQueries: true,
            lookupFilter: _lookupFilter,
            paramName: "query",
            transformResult: _transformResult,
            showNoSuggestionNotice: false,
            noSuggestionNotice: "No results",
            orientation: "bottom",
            forceFixPosition: false,
        };

        function _lookupFilter(suggestion, originalQuery, queryLowerCase) {
            return suggestion.value.toLowerCase().indexOf(queryLowerCase) !== -1;
        }

        function _transformResult(response) {
            return typeof response === "string" ? JSON.parse(response) : response;
        }

        function _formatResult(suggestion, currentValue) {
            // Do not replace anything if the current value is empty
            if (!currentValue) {
                return suggestion.value;
            }

            var pattern = "(" + utils.escapeRegExChars(currentValue) + ")";

            return suggestion.value
                .replace(new RegExp(pattern, "gi"), "<strong>$1</strong>")
                .replace(/&/g, "&amp;")
                .replace(/</g, "&lt;")
                .replace(/>/g, "&gt;")
                .replace(/"/g, "&quot;")
                .replace(/&lt;(\/?strong)&gt;/g, "<$1>");
        }

        function _formatGroup(suggestion, category) {
            return '<div class="autocomplete-group">' + category + "</div>";
        }

        Autocomplete.prototype = {
            initialize: function () {
                var that = this,
                    suggestionSelector = "." + that.classes.suggestion,
                    selected = that.classes.selected,
                    options = that.options,
                    container;

                that.element.setAttribute("autocomplete", "off");

                // html() deals with many types: htmlString or Element or Array or jQuery
                that.noSuggestionsContainer = $('<div class="autocomplete-no-suggestion"></div>')
                    .html(this.options.noSuggestionNotice)
                    .get(0);

                that.suggestionsContainer = Autocomplete.utils.createNode(options.containerClass);

                container = $(that.suggestionsContainer);

                container.appendTo(options.appendTo || "body");

                // Only set width if it was provided:
                if (options.width !== "auto") {
                    container.css("width", options.width);
                }

                // Listen for mouse over event on suggestions list:
                container.on("mouseover.autocomplete", suggestionSelector, function () {
                    that.activate($(this).data("index"));
                });

                // Deselect active element when mouse leaves suggestions container:
                container.on("mouseout.autocomplete", function () {
                    that.selectedIndex = -1;
                    container.children("." + selected).removeClass(selected);
                });

                // Listen for click event on suggestions list:
                container.on("click.autocomplete", suggestionSelector, function () {
                    that.select($(this).data("index"));
                });

                container.on("click.autocomplete", function () {
                    clearTimeout(that.blurTimeoutId);
                });

                that.fixPositionCapture = function () {
                    if (that.visible) {
                        that.fixPosition();
                    }
                };

                $(window).on("resize.autocomplete", that.fixPositionCapture);

                that.el.on("keydown.autocomplete", function (e) {
                    that.onKeyPress(e);
                });
                that.el.on("keyup.autocomplete", function (e) {
                    that.onKeyUp(e);
                });
                that.el.on("blur.autocomplete", function () {
                    that.onBlur();
                });
                that.el.on("focus.autocomplete", function () {
                    that.onFocus();
                });
                that.el.on("change.autocomplete", function (e) {
                    that.onKeyUp(e);
                });
                that.el.on("input.autocomplete", function (e) {
                    that.onKeyUp(e);
                });
            },

            onFocus: function () {
                var that = this;

                if (that.disabled) {
                    return;
                }

                that.fixPosition();

                if (that.el.val().length >= that.options.minChars) {
                    that.onValueChange();
                }
            },

            onBlur: function () {
                var that = this,
                    options = that.options,
                    value = that.el.val(),
                    query = that.getQuery(value);

                // If user clicked on a suggestion, hide() will
                // be canceled, otherwise close suggestions
                that.blurTimeoutId = setTimeout(function () {
                    that.hide();

                    if (that.selection && that.currentValue !== query) {
                        (options.onInvalidateSelection || $.noop).call(that.element);
                    }
                }, 200);
            },

            abortAjax: function () {
                var that = this;
                if (that.currentRequest) {
                    that.currentRequest.abort();
                    that.currentRequest = null;
                }
            },

            setOptions: function (suppliedOptions) {
                var that = this,
                    options = $.extend({}, that.options, suppliedOptions);

                that.isLocal = Array.isArray(options.lookup);

                if (that.isLocal) {
                    options.lookup = that.verifySuggestionsFormat(options.lookup);
                }

                options.orientation = that.validateOrientation(options.orientation, "bottom");

                // Adjust height, width and z-index:
                $(that.suggestionsContainer).css({
                    "max-height": options.maxHeight + "px",
                    width: options.width + "px",
                    "z-index": options.zIndex,
                });

                this.options = options;
            },

            clearCache: function () {
                this.cachedResponse = {};
                this.badQueries = [];
            },

            clear: function () {
                this.clearCache();
                this.currentValue = "";
                this.suggestions = [];
            },

            disable: function () {
                var that = this;
                that.disabled = true;
                clearTimeout(that.onChangeTimeout);
                that.abortAjax();
            },

            enable: function () {
                this.disabled = false;
            },

            fixPosition: function () {
                // Use only when container has already its content

                var that = this,
                    $container = $(that.suggestionsContainer),
                    containerParent = $container.parent().get(0);
                // Fix position automatically when appended to body.
                // In other cases force parameter must be given.
                if (containerParent !== document.body && !that.options.forceFixPosition) {
                    return;
                }

                // Choose orientation
                var orientation = that.options.orientation,
                    containerHeight = $container.outerHeight(),
                    height = that.el.outerHeight(),
                    offset = that.el.offset(),
                    styles = { top: offset.top, left: offset.left };

                if (orientation === "auto") {
                    var viewPortHeight = $(window).height(),
                        scrollTop = $(window).scrollTop(),
                        topOverflow = -scrollTop + offset.top - containerHeight,
                        bottomOverflow =
                            scrollTop + viewPortHeight - (offset.top + height + containerHeight);

                    orientation =
                        Math.max(topOverflow, bottomOverflow) === topOverflow ? "top" : "bottom";
                }

                if (orientation === "top") {
                    styles.top += -containerHeight;
                } else {
                    styles.top += height;
                }

                // If container is not positioned to body,
                // correct its position using offset parent offset
                if (containerParent !== document.body) {
                    var opacity = $container.css("opacity"),
                        parentOffsetDiff;

                    if (!that.visible) {
                        $container.css("opacity", 0).show();
                    }

                    parentOffsetDiff = $container.offsetParent().offset();
                    styles.top -= parentOffsetDiff.top;
                    styles.top += containerParent.scrollTop;
                    styles.left -= parentOffsetDiff.left;

                    if (!that.visible) {
                        $container.css("opacity", opacity).hide();
                    }
                }

                if (that.options.width === "auto") {
                    styles.width = that.el.outerWidth() + "px";
                }

                $container.css(styles);
            },

            isCursorAtEnd: function () {
                var that = this,
                    valLength = that.el.val().length,
                    selectionStart = that.element.selectionStart,
                    range;

                if (typeof selectionStart === "number") {
                    return selectionStart === valLength;
                }
                if (document.selection) {
                    range = document.selection.createRange();
                    range.moveStart("character", -valLength);
                    return valLength === range.text.length;
                }
                return true;
            },

            onKeyPress: function (e) {
                var that = this;

                // If suggestions are hidden and user presses arrow down, display suggestions:
                if (!that.disabled && !that.visible && e.which === keys.DOWN && that.currentValue) {
                    that.suggest();
                    return;
                }

                if (that.disabled || !that.visible) {
                    return;
                }

                switch (e.which) {
                    case keys.ESC:
                        that.el.val(that.currentValue);
                        that.hide();
                        break;
                    case keys.RIGHT:
                        if (that.hint && that.options.onHint && that.isCursorAtEnd()) {
                            that.selectHint();
                            break;
                        }
                        return;
                    case keys.TAB:
                        if (that.hint && that.options.onHint) {
                            that.selectHint();
                            return;
                        }
                        if (that.selectedIndex === -1) {
                            that.hide();
                            return;
                        }
                        that.select(that.selectedIndex);
                        if (that.options.tabDisabled === false) {
                            return;
                        }
                        break;
                    case keys.RETURN:
                        if (that.selectedIndex === -1) {
                            that.hide();
                            return;
                        }
                        that.select(that.selectedIndex);
                        break;
                    case keys.UP:
                        that.moveUp();
                        break;
                    case keys.DOWN:
                        that.moveDown();
                        break;
                    default:
                        return;
                }

                // Cancel event if function did not return:
                e.stopImmediatePropagation();
                e.preventDefault();
            },

            onKeyUp: function (e) {
                var that = this;

                if (that.disabled) {
                    return;
                }

                switch (e.which) {
                    case keys.UP:
                    case keys.DOWN:
                        return;
                }

                clearTimeout(that.onChangeTimeout);

                if (that.currentValue !== that.el.val()) {
                    that.findBestHint();
                    if (that.options.deferRequestBy > 0) {
                        // Defer lookup in case when value changes very quickly:
                        that.onChangeTimeout = setTimeout(function () {
                            that.onValueChange();
                        }, that.options.deferRequestBy);
                    } else {
                        that.onValueChange();
                    }
                }
            },

            onValueChange: function () {
                if (this.ignoreValueChange) {
                    this.ignoreValueChange = false;
                    return;
                }

                var that = this,
                    options = that.options,
                    value = that.el.val(),
                    query = that.getQuery(value);

                if (that.selection && that.currentValue !== query) {
                    that.selection = null;
                    (options.onInvalidateSelection || $.noop).call(that.element);
                }

                clearTimeout(that.onChangeTimeout);
                that.currentValue = value;
                that.selectedIndex = -1;

                // Check existing suggestion for the match before proceeding:
                if (options.triggerSelectOnValidInput && that.isExactMatch(query)) {
                    that.select(0);
                    return;
                }

                if (query.length < options.minChars) {
                    that.hide();
                } else {
                    that.getSuggestions(query);
                }
            },

            isExactMatch: function (query) {
                var suggestions = this.suggestions;

                return (
                    suggestions.length === 1 &&
                    suggestions[0].value.toLowerCase() === query.toLowerCase()
                );
            },

            getQuery: function (value) {
                var delimiter = this.options.delimiter,
                    parts;

                if (!delimiter) {
                    return value;
                }
                parts = value.split(delimiter);
                return parts[parts.length - 1].trim();
            },

            getSuggestionsLocal: function (query) {
                var that = this,
                    options = that.options,
                    queryLowerCase = query.toLowerCase(),
                    filter = options.lookupFilter,
                    limit = parseInt(options.lookupLimit, 10),
                    data;

                data = {
                    suggestions: $.grep(options.lookup, function (suggestion) {
                        return filter(suggestion, query, queryLowerCase);
                    }),
                };

                if (limit && data.suggestions.length > limit) {
                    data.suggestions = data.suggestions.slice(0, limit);
                }

                return data;
            },

            getSuggestions: function (q) {
                var response,
                    that = this,
                    options = that.options,
                    serviceUrl = options.serviceUrl,
                    params,
                    cacheKey,
                    ajaxSettings;

                options.params[options.paramName] = q;

                if (options.onSearchStart.call(that.element, options.params) === false) {
                    return;
                }

                params = options.ignoreParams ? null : options.params;

                if (typeof options.lookup === "function") {
                    options.lookup(q, function (data) {
                        that.suggestions = data.suggestions;
                        that.suggest();
                        options.onSearchComplete.call(that.element, q, data.suggestions);
                    });
                    return;
                }

                if (that.isLocal) {
                    response = that.getSuggestionsLocal(q);
                } else {
                    if (typeof serviceUrl === "function") {
                        serviceUrl = serviceUrl.call(that.element, q);
                    }
                    cacheKey = serviceUrl + "?" + $.param(params || {});
                    response = that.cachedResponse[cacheKey];
                }

                if (response && Array.isArray(response.suggestions)) {
                    that.suggestions = response.suggestions;
                    that.suggest();
                    options.onSearchComplete.call(that.element, q, response.suggestions);
                } else if (!that.isBadQuery(q)) {
                    that.abortAjax();

                    ajaxSettings = {
                        url: serviceUrl,
                        data: params,
                        type: options.type,
                        dataType: options.dataType,
                    };

                    $.extend(ajaxSettings, options.ajaxSettings);

                    that.currentRequest = $.ajax(ajaxSettings)
                        .done(function (data) {
                            var result;
                            that.currentRequest = null;
                            result = options.transformResult(data, q);
                            that.processResponse(result, q, cacheKey);
                            options.onSearchComplete.call(that.element, q, result.suggestions);
                        })
                        .fail(function (jqXHR, textStatus, errorThrown) {
                            options.onSearchError.call(
                                that.element,
                                q,
                                jqXHR,
                                textStatus,
                                errorThrown
                            );
                        });
                } else {
                    options.onSearchComplete.call(that.element, q, []);
                }
            },

            isBadQuery: function (q) {
                if (!this.options.preventBadQueries) {
                    return false;
                }

                var badQueries = this.badQueries,
                    i = badQueries.length;

                while (i--) {
                    if (q.indexOf(badQueries[i]) === 0) {
                        return true;
                    }
                }

                return false;
            },

            hide: function () {
                var that = this,
                    container = $(that.suggestionsContainer);

                if (typeof that.options.onHide === "function" && that.visible) {
                    that.options.onHide.call(that.element, container);
                }

                that.visible = false;
                that.selectedIndex = -1;
                clearTimeout(that.onChangeTimeout);
                $(that.suggestionsContainer).hide();
                that.onHint(null);
            },

            suggest: function () {
                if (!this.suggestions.length) {
                    if (this.options.showNoSuggestionNotice) {
                        this.noSuggestions();
                    } else {
                        this.hide();
                    }
                    return;
                }

                var that = this,
                    options = that.options,
                    groupBy = options.groupBy,
                    formatResult = options.formatResult,
                    value = that.getQuery(that.currentValue),
                    className = that.classes.suggestion,
                    classSelected = that.classes.selected,
                    container = $(that.suggestionsContainer),
                    noSuggestionsContainer = $(that.noSuggestionsContainer),
                    beforeRender = options.beforeRender,
                    html = "",
                    category,
                    formatGroup = function (suggestion) {
                        var currentCategory = suggestion.data[groupBy];

                        if (category === currentCategory) {
                            return "";
                        }

                        category = currentCategory;

                        return options.formatGroup(suggestion, category);
                    };

                if (options.triggerSelectOnValidInput && that.isExactMatch(value)) {
                    that.select(0);
                    return;
                }

                // Build suggestions inner HTML:
                $.each(that.suggestions, function (i, suggestion) {
                    if (groupBy) {
                        html += formatGroup(suggestion, value, i);
                    }

                    html +=
                        '<div class="' +
                        className +
                        '" data-index="' +
                        i +
                        '">' +
                        formatResult(suggestion, value, i) +
                        "</div>";
                });

                this.adjustContainerWidth();

                noSuggestionsContainer.detach();
                container.html(html);

                if (typeof beforeRender === "function") {
                    beforeRender.call(that.element, container, that.suggestions);
                }

                that.fixPosition();
                container.show();

                // Select first value by default:
                if (options.autoSelectFirst) {
                    that.selectedIndex = 0;
                    container.scrollTop(0);
                    container
                        .children("." + className)
                        .first()
                        .addClass(classSelected);
                }

                that.visible = true;
                that.findBestHint();
            },

            noSuggestions: function () {
                var that = this,
                    beforeRender = that.options.beforeRender,
                    container = $(that.suggestionsContainer),
                    noSuggestionsContainer = $(that.noSuggestionsContainer);

                this.adjustContainerWidth();

                // Some explicit steps. Be careful here as it easy to get
                // noSuggestionsContainer removed from DOM if not detached properly.
                noSuggestionsContainer.detach();

                // clean suggestions if any
                container.empty();
                container.append(noSuggestionsContainer);

                if (typeof beforeRender === "function") {
                    beforeRender.call(that.element, container, that.suggestions);
                }

                that.fixPosition();

                container.show();
                that.visible = true;
            },

            adjustContainerWidth: function () {
                var that = this,
                    options = that.options,
                    width,
                    container = $(that.suggestionsContainer);

                // If width is auto, adjust width before displaying suggestions,
                // because if instance was created before input had width, it will be zero.
                // Also it adjusts if input width has changed.
                if (options.width === "auto") {
                    width = that.el.outerWidth();
                    container.css("width", width > 0 ? width : 300);
                } else if (options.width === "flex") {
                    // Trust the source! Unset the width property so it will be the max length
                    // the containing elements.
                    container.css("width", "");
                }
            },

            findBestHint: function () {
                var that = this,
                    value = that.el.val().toLowerCase(),
                    bestMatch = null;

                if (!value) {
                    return;
                }

                $.each(that.suggestions, function (i, suggestion) {
                    var foundMatch = suggestion.value.toLowerCase().indexOf(value) === 0;
                    if (foundMatch) {
                        bestMatch = suggestion;
                    }
                    return !foundMatch;
                });

                that.onHint(bestMatch);
            },

            onHint: function (suggestion) {
                var that = this,
                    onHintCallback = that.options.onHint,
                    hintValue = "";

                if (suggestion) {
                    hintValue =
                        that.currentValue + suggestion.value.substr(that.currentValue.length);
                }
                if (that.hintValue !== hintValue) {
                    that.hintValue = hintValue;
                    that.hint = suggestion;
                    if (typeof onHintCallback === "function") {
                        onHintCallback.call(that.element, hintValue);
                    }
                }
            },

            verifySuggestionsFormat: function (suggestions) {
                // If suggestions is string array, convert them to supported format:
                if (suggestions.length && typeof suggestions[0] === "string") {
                    return $.map(suggestions, function (value) {
                        return { value: value, data: null };
                    });
                }

                return suggestions;
            },

            validateOrientation: function (orientation, fallback) {
                orientation = (orientation || "").trim().toLowerCase();

                if ($.inArray(orientation, ["auto", "bottom", "top"]) === -1) {
                    orientation = fallback;
                }

                return orientation;
            },

            processResponse: function (result, originalQuery, cacheKey) {
                var that = this,
                    options = that.options;

                result.suggestions = that.verifySuggestionsFormat(result.suggestions);

                // Cache results if cache is not disabled:
                if (!options.noCache) {
                    that.cachedResponse[cacheKey] = result;
                    if (options.preventBadQueries && !result.suggestions.length) {
                        that.badQueries.push(originalQuery);
                    }
                }

                // Return if originalQuery is not matching current query:
                if (originalQuery !== that.getQuery(that.currentValue)) {
                    return;
                }

                that.suggestions = result.suggestions;
                that.suggest();
            },

            activate: function (index) {
                var that = this,
                    activeItem,
                    selected = that.classes.selected,
                    container = $(that.suggestionsContainer),
                    children = container.find("." + that.classes.suggestion);

                container.find("." + selected).removeClass(selected);

                that.selectedIndex = index;

                if (that.selectedIndex !== -1 && children.length > that.selectedIndex) {
                    activeItem = children.get(that.selectedIndex);
                    $(activeItem).addClass(selected);
                    return activeItem;
                }

                return null;
            },

            selectHint: function () {
                var that = this,
                    i = $.inArray(that.hint, that.suggestions);

                that.select(i);
            },

            select: function (i) {
                var that = this;
                that.hide();
                that.onSelect(i);
            },

            moveUp: function () {
                var that = this;

                if (that.selectedIndex === -1) {
                    return;
                }

                if (that.selectedIndex === 0) {
                    $(that.suggestionsContainer)
                        .children("." + that.classes.suggestion)
                        .first()
                        .removeClass(that.classes.selected);
                    that.selectedIndex = -1;
                    that.ignoreValueChange = false;
                    that.el.val(that.currentValue);
                    that.findBestHint();
                    return;
                }

                that.adjustScroll(that.selectedIndex - 1);
            },

            moveDown: function () {
                var that = this;

                if (that.selectedIndex === that.suggestions.length - 1) {
                    return;
                }

                that.adjustScroll(that.selectedIndex + 1);
            },

            adjustScroll: function (index) {
                var that = this,
                    activeItem = that.activate(index);

                if (!activeItem) {
                    return;
                }

                var offsetTop,
                    upperBound,
                    lowerBound,
                    heightDelta = $(activeItem).outerHeight();

                offsetTop = activeItem.offsetTop;
                upperBound = $(that.suggestionsContainer).scrollTop();
                lowerBound = upperBound + that.options.maxHeight - heightDelta;

                if (offsetTop < upperBound) {
                    $(that.suggestionsContainer).scrollTop(offsetTop);
                } else if (offsetTop > lowerBound) {
                    $(that.suggestionsContainer).scrollTop(
                        offsetTop - that.options.maxHeight + heightDelta
                    );
                }

                if (!that.options.preserveInput) {
                    // During onBlur event, browser will trigger "change" event,
                    // because value has changed, to avoid side effect ignore,
                    // that event, so that correct suggestion can be selected
                    // when clicking on suggestion with a mouse
                    that.ignoreValueChange = true;
                    that.el.val(that.getValue(that.suggestions[index].value));
                }

                that.onHint(null);
            },

            onSelect: function (index) {
                var that = this,
                    onSelectCallback = that.options.onSelect,
                    suggestion = that.suggestions[index];

                that.currentValue = that.getValue(suggestion.value);

                if (that.currentValue !== that.el.val() && !that.options.preserveInput) {
                    that.el.val(that.currentValue);
                }

                that.onHint(null);
                that.suggestions = [];
                that.selection = suggestion;

                if (typeof onSelectCallback === "function") {
                    onSelectCallback.call(that.element, suggestion);
                }
            },

            getValue: function (value) {
                var that = this,
                    delimiter = that.options.delimiter,
                    currentValue,
                    parts;

                if (!delimiter) {
                    return value;
                }

                currentValue = that.currentValue;
                parts = currentValue.split(delimiter);

                if (parts.length === 1) {
                    return value;
                }

                return (
                    currentValue.substr(0, currentValue.length - parts[parts.length - 1].length) +
                    value
                );
            },

            dispose: function () {
                var that = this;
                that.el.off(".autocomplete").removeData("autocomplete");
                $(window).off("resize.autocomplete", that.fixPositionCapture);
                $(that.suggestionsContainer).remove();
            },
        };

        // Create chainable jQuery plugin:
        $.fn.devbridgeAutocomplete = function (options, args) {
            var dataKey = "autocomplete";
            // If function invoked without argument return
            // instance of the first matched element:
            if (!arguments.length) {
                return this.first().data(dataKey);
            }

            return this.each(function () {
                var inputElement = $(this),
                    instance = inputElement.data(dataKey);

                if (typeof options === "string") {
                    if (instance && typeof instance[options] === "function") {
                        instance[options](args);
                    }
                } else {
                    // If instance already exists, destroy it:
                    if (instance && instance.dispose) {
                        instance.dispose();
                    }
                    instance = new Autocomplete(this, options);
                    inputElement.data(dataKey, instance);
                }
            });
        };

        // Don't overwrite if it already exists
        if (!$.fn.autocomplete) {
            $.fn.autocomplete = $.fn.devbridgeAutocomplete;
        }
    }
);
